// file: system/task/mutexqueue.c
// autor: jiangxinpeng
// time: 2021.4.3
// copyright: (C) 2020-2050 by jiangxinpeng,All right are reserved.

#include<arch/x86.h>
#include <os/mutexqueue.h>
#include <os/memcache.h>
#include <os/debug.h>
#include <os/mutexlock.h>
#include <os/schedule.h>
#include <os/safety.h>
#include <os/spinlock.h>
#include <os/clock.h>
#include <os/timer.h>
#include <os/sleep.h>
#include <os/task.h>
#include <sys/time.h>
#include <lib/errno.h>

mutex_queue_t *mutex_queue_list;
DEFINE_SPIN_LOCK(mutex_queue_list_lock);

static int MutexQueueAddrCalc(void *addr, uint32_t wqflags, uint64_t val)
{
    uint64_t tmpval;
    switch (wqflags&MUTEX_QUEUE_OPMASK)
    {
    case MUTEX_QUEUE_ADD:
        if (MemCopyFromUser((void *)&tmpval, addr, sizeof(uint64_t)) < 0)
            return -1;
        tmpval += val;
        if (MemCopyToUser(addr, (void *)&tmpval, sizeof(uint64_t)) < 0)
            return -1;
        break;
    case MUTEX_QUEUE_SUB:
        if (MemCopyFromUser((void *)&tmpval, addr, sizeof(uint64_t)) < 0)
            return -1;
        tmpval -= val;
        if (MemCopyToUser(addr, (void *)&tmpval, sizeof(uint64_t)) < 0)
            return -1;
        break;
    case MUTEX_QUEUE_SET:
        tmpval = val;
        if (MemCopyToUser(addr, (void *)&tmpval, sizeof(uint64_t)) < 0)
            return -1;
        break;
    case MUTEX_QUEUE_ZERO:
        tmpval = 0;
        if (MemCopyToUser(addr, (void *)&tmpval, sizeof(uint64_t)) < 0)
            return -1;
        break;
    case MUTEX_QUEUE_ONE:
        tmpval = 1;
        if (MemCopyToUser(addr, (void *)&tmpval, sizeof(uint64_t)) < 0)
            return -1;
        break;
    case MUTEX_QUEUE_INC:
        if (MemCopyFromUser((void *)&tmpval, addr, sizeof(uint64_t)) < 0)
            return -1;
        ++tmpval;
        if (MemCopyToUser(addr, (void *)&tmpval, sizeof(uint64_t)) < 0)
            return -1;
        break;
    case MUTEX_QUEUE_DEC:
        if (MemCopyFromUser((void *)&tmpval, addr, sizeof(uint64_t)) < 0)
            return -1;
        --tmpval;
        if (MemCopyToUser(addr, (void *)&tmpval, sizeof(uint64_t)) < 0)
            return -1;
        break;
    default:
        break;
    }
    return 0;
}

void MutexQueueInit()
{
    mutex_queue_list = KMemAlloc(sizeof(mutex_queue_t) * MUTEX_QUEUE_NUM_MAX);
    if (!mutex_queue_list)
    {
        KPrint(PRINT_ERR "alloc memory for mutex queue failed!\n");
    }
    for (int i = 0; i < MUTEX_QUEUE_NUM_MAX; i++)
    {
        WaitQueueInit(&mutex_queue_list[i].wait_queue);
        mutex_queue_list[i].flags = 0;
    }
    KPrint("[mutexqueue] init mutext queue ok!\n");
}

int SysMutexQueueAlloc()
{
    int i;
    mutex_queue_t *mutex_queue = mutex_queue_list;

    SpinLock(&mutex_queue_list_lock);
    for (i = 0; i < MUTEX_QUEUE_NUM_MAX; i++)
    {
        if (!mutex_queue->flags)
        {
            WaitQueueInit(&mutex_queue->wait_queue);
            mutex_queue->flags = MUTEX_QUEUE_USING;
            SpinUnlock(&mutex_queue_list_lock);
            return i;
        }
        mutex_queue++;
    }
    SpinUnlock(&mutex_queue_list_lock);
    return -1;
}

int SysMutexQueueFree(int handle)
{
    mutex_queue_t *mutex_queue;

    if (MUTEX_QUEUE_IS_BAD(handle))
        return -EINVAL;

    SpinLock(&mutex_queue_list_lock);
    mutex_queue = mutex_queue_list + handle;
    if (!mutex_queue->flags)
    {
        while (!list_empty(&mutex_queue->wait_queue.wait_list))
        {
            WaitQueueWakeupAll(&mutex_queue->wait_queue);
        }
        mutex_queue->flags = 0;
    }
    SpinUnlock(&mutex_queue_list_lock);
    return 0;
}

/**
 * @handle: 等待队列句柄
 * @addr: 需要操作的数据地址
 * @wqflags: 操作标志：
 *          MUTEX_QUEUE_SET： 设置addr里面的值为value
 *          MUTEX_QUEUE_ADD： 往addr里面的值加上value
 *          MUTEX_QUEUE_SUB： 往addr里面的值减去value
 * @value: 参数变量
 *
 * 唤醒第一个等待中的任务
 * 修改值和阻塞的过程是原子操作
 */
int SysMutexQueueWake(int handle, void *addr, uint32_t wqflags,uint32_t value)
{
    if (MUTEX_QUEUE_IS_BAD(handle))
        return -EINVAL;

    uint32_t eflags=InterruptDisableStore();
    mutex_queue_t *mutex_queue = &mutex_queue_list[handle];
    if (mutex_queue->flags)
    {
        if (addr)
        {
            if (MutexQueueAddrCalc(addr, wqflags, value) < 0)
            {
                InterruptEnableRestore(eflags);
                return -EFAULT;
            }
        }

        task_t *task, *next;

        if (list_empty(&mutex_queue->wait_queue.wait_list))
        {
            InterruptEnableRestore(eflags);
            return 0;
        }

        list_traversal_all_owner_to_next_safe(task, next, &mutex_queue->wait_queue.wait_list, list)
        {
            list_del(&task->list);
            //TASK_EXIT_WAITLIST(task);
            TaskWakeUp(task);
            if (wqflags & MUTEX_QUEUE_ALL) // whether needed wakeup all task
            {
                continue;
            }
            else
            {
                break;
            }
        }
    }
    InterruptEnableRestore(eflags);
    return 0;
}

//this function is not suitable to use spin or mutex lock,because unable to both unlock and ensure atomic while timeouts operator 
int SysMutexQueueWait(int handle, void *addr, uint32_t wqflags, uint32_t val)
{
    mutex_queue_t *mutex_queue;
    clock_t ticks = 0;
    timespec_t abstime, curtime, newtime;


    if (MUTEX_QUEUE_IS_BAD(handle))
        return -EINVAL;

    TASK_CHECK_THREAD_CANNELATION(cur_task);

    uint32_t eflags= InterruptDisableStore();
    mutex_queue = mutex_queue_list + handle;
    if (mutex_queue->flags)
    {
        // operate data addr
        if (addr)
        {
            if (MutexQueueAddrCalc(addr, wqflags, val) < 0)
            {
                InterruptEnableRestore(eflags);
                return -EINVAL;
            }
        }

        // timeout operator
        if (wqflags & MUTEX_QUEUE_TIMED)
        {
            if (val)
            { 
                if (MemCopyFromUser(&abstime, (timespec_t *)(uint32_t)val, sizeof(timespec_t)))
                {
                    InterruptEnableRestore(eflags);
                    return -EINVAL;
                }
              
                ticks = TimespecToSysticks(&abstime);     

                if (ticks <= 0)
                {
                    InterruptEnableRestore(eflags);
                    return -ETIMEDOUT;
                }
                if (ticks < 2 * MS_PER_TICKS)
                    ticks = 2 * MS_PER_TICKS;
                WaitQueueAdd(&mutex_queue->wait_queue, cur_task);
                if (TaskSleepByTicks(ticks) > 0)
                {
                    InterruptEnableRestore(eflags);
                    return 0;
                }
                else
                {
                    task_t *task, *next;
                    list_traversal_all_owner_to_next_safe(task, next, &mutex_queue->wait_queue.wait_list, list)
                    {
                        if (task == cur_task)
                            list_del(&task->list);
                    }
                    InterruptEnableRestore(eflags);
                    return -ETIMEDOUT;
                }
            }
        }
        else
        {
            WaitQueueAdd(&mutex_queue->wait_queue, cur_task);        
            InterruptEnableRestore(eflags);
            TaskBlock(TASK_BLOCKED);
            return 0;
        }
    }
    InterruptEnableRestore(eflags);
    return 0;
}
